package com.jh.fcsm.service.sys.impl;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.jh.fcsm.beans.sys.SysRole;
import com.jh.fcsm.beans.sys.SysUser;
import com.jh.fcsm.beans.sys.SysUserRole;
import com.jh.fcsm.beans.sys.vo.SysUserVo;
import com.jh.fcsm.common.BaseServiceImpl;
import com.jh.fcsm.common.exception.ServiceException;
import com.jh.fcsm.common.redis.RedisUtil;
import com.jh.fcsm.constant.CommonConstant;
import com.jh.fcsm.constant.Constant;
import com.jh.fcsm.constant.RedisConstant;
import com.jh.fcsm.constant.TokenConstants;
import com.jh.fcsm.dao.sys.SysRoleMapper;
import com.jh.fcsm.dao.sys.SysRolePermissionMapper;
import com.jh.fcsm.dao.sys.SysUserMapper;
import com.jh.fcsm.dao.sys.SysUserRoleMapper;
import com.jh.fcsm.service.sys.LoginAttemptService;
import com.jh.fcsm.service.sys.RoleService;
import com.jh.fcsm.service.sys.SysUserService;
import com.jh.fcsm.util.AssertUtil;
import com.jh.fcsm.util.security.GoogleAuthenticationTool;
import com.jh.fcsm.util.security.RSAEncrypt;
import com.jh.fcsm.util.security.UUIDUtils;
import com.jh.fcsm.util.txt.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import tk.mybatis.mapper.entity.Example;

import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 系统用户表
 *
 * @author szx
 * @date 2020-07-05 16:25:12
 */
@Service
public class SysUserServiceImpl extends BaseServiceImpl<SysUserMapper, SysUser> implements SysUserService {

    public static final Logger logger = LoggerFactory.getLogger(SysUserServiceImpl.class);

    // 正则表达式：数字+字母+特殊字符

    @Autowired
    private SysUserMapper sysUserMapper;
    @Autowired
    private SysRoleMapper sysRoleMapper;
    @Autowired
    private RoleService roleService;
    @Autowired
    private SysRolePermissionMapper sysRolePermissionMapper;
    @Autowired
    private SysUserRoleMapper sysUserRoleMapper;
    @Autowired
    private LoginAttemptService loginAttemptService;

    @Autowired
    private RedisUtil redisUtil;

    private final BCryptPasswordEncoder bp = new BCryptPasswordEncoder();

    /**
     * 保存或更新系统用户表
     *
     * @param sysUser 用户对象
     * @return String 用户ID
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, rollbackFor = {RuntimeException.class})
    public String saveOrUpdate(SysUserVo sysUser) {
        AssertUtil.notNull(sysUser);
        // 新增操作
        if (StringUtils.isEmpty(sysUser.getId())) {
            // 数据校验
            checkData(sysUser);
            // 验证用户名和手机号的唯一性
            validateUsernameAndPhone(sysUser.getUsername(), sysUser.getPhone());
            SysUser addUser = new SysUser();
            // 封装用户数据
            encapSysUser(sysUser, addUser);
            sysUserMapper.insertSelective(addUser);
        } else {
            // 更新操作
            SysUser updateUser = sysUserMapper.selectByPrimaryKey(sysUser.getId());
            AssertUtil.notNull(updateUser, "用户不存在或已删除");
            // 封装用户数据
            encapSysUser(sysUser, updateUser);
            sysUserMapper.updateByPrimaryKeySelective(updateUser);
        }

        // 保存用户和角色关系
        saveUserRoleToUserinfo(sysUser);
        return sysUser.getId();
    }

    /**
     * 保存用户角色关联关系
     *
     * @param sysUser 用户对象
     */
    private void saveUserRoleToUserinfo(SysUserVo sysUser) {
        List<String> roleIds = sysUser.getRoleIds();
        // 先删除所有用户和角色关联关系
        Example example = new Example(SysUserRole.class);
        example.createCriteria().andEqualTo("userId", sysUser.getId());
        sysUserRoleMapper.deleteByExample(example);
        // 新增用户和角色关联关系
        if (CollectionUtils.isEmpty(roleIds)) {
            return;
        }
        for (String roleId : roleIds) {
            SysUserRole sysUserRole = new SysUserRole();
            sysUserRole.setRoleId(roleId);
            sysUserRole.setUserId(sysUser.getId());
            sysUserRoleMapper.insert(sysUserRole);
        }
    }

    /**
     * 验证用户名和手机号是否唯一
     *
     * @param username 用户名
     * @param phone    手机号
     */
    private void validateUsernameAndPhone(String username, String phone) {
        int count = sysUserMapper.countUserByUsername(username);
        if (count > 0) {
            throw new ServiceException("登录名重复");
        }
        // 根据用户的联系电话 查询phone是否重复
        int countPhone = sysUserMapper.countUserByPhone(phone);
        if (countPhone > 0) {
            throw new ServiceException("手机号已存在");
        }
    }

    /**
     * 对前端提交的数据做校验
     *
     * @param sysUser 前端提交的数据
     */
    private void checkData(SysUserVo sysUser) {
        // 校验用户名
        if (StringUtils.isEmpty(sysUser.getUsername())) {
            throw new ServiceException("用户登录名必须填写");
        }
        String trim = sysUser.getUsername().trim();
        if (trim.length() > Constant.USER_NAME_LENGTH) {
            throw new ServiceException("用户名不得超过" + Constant.USER_NAME_LENGTH + "位");
        }
        sysUser.setUsername(trim);
        // 校验用户密码
        if (StringUtils.isEmpty(sysUser.getPassword())) {
            throw new ServiceException("用户密码必须填写");
        }
        if (sysUser.getPassword().length() > Constant.USER_PASSWORD_LENGTH) {
            throw new ServiceException("用户密码长度不得超过" + Constant.USER_PASSWORD_LENGTH + "位");
        }
        if (sysUser.getPassword().length() < Constant.MINSIZE) {
            throw new ServiceException("用户密码长度不得少于" + Constant.MINSIZE + "位");
        }
        // 新密码正则校验
        if (!Pattern.matches(CommonConstant.REGCH, new String(Base64.getDecoder().decode(sysUser.getPassword())))) {
            throw new ServiceException("您的新密码复杂度太低（密码中必须包含字母、数字、特殊字符）");
        }
    }

    /**
     * 将页面提交的数据封装到对应的bean中
     *
     * @param sysUser 页面提交的数据
     * @param addUser 对应的bean
     */
    private void encapSysUser(SysUserVo sysUser, SysUser addUser) {
        if (StringUtils.isEmpty(sysUser.getId())) {
            addUser.setId(UUIDUtils.getUUID());
        } else {
            addUser.setId(sysUser.getId());
        }

        addUser.setNickname(sysUser.getNickname());
        addUser.setUsername(sysUser.getUsername());
        if (!StringUtils.isEmpty(sysUser.getPassword())) {
            // 密码加密
            byte[] decode = Base64.getDecoder().decode(sysUser.getPassword());
            String password = new String(decode);
            password = bp.encode(password);
            addUser.setPassword(password);
        }
        addUser.setPhone(sysUser.getPhone());
        addUser.setRemark(sysUser.getRemark());
        addUser.setYn(1);
        addUser.setEnabled(1);
        if (!StringUtils.isEmpty(sysUser.getUserId())) {
            addUser.setId(sysUser.getUserId());
        }
        sysUser.setId(addUser.getId());
        addUser.setType(sysUser.getType());
        addUser.setTypeName(sysUser.getTypeName());
    }

    /**
     * 删除系统用户表
     *
     * @param ids 系统用户表ID
     */
    @Override
    @Transactional(readOnly = false)
    public void deleteSysUser(List<String> ids) {
        AssertUtil.notNull(ids);
        sysUserMapper.updateYnByIds(ids);
    }

    /**
     * 查询系统用户表详情
     *
     * @param id 用户ID
     * @return 用户信息
     */
    @Override
    public SysUser findById(String id) {
        AssertUtil.notNull(id);
        SysUser user = sysUserMapper.selectByPrimaryKey(id);
        AssertUtil.notNull(user, "用户不存在");
        return user;
    }

    /**
     * 分页查询用户
     *
     * @param sysUserVo 用户对象
     * @return 分页数据
     */
    @Override
    public PageInfo<SysUserVo> findPageByQuery(SysUserVo sysUserVo) {
        AssertUtil.notNull(sysUserVo);
        PageHelper.startPage(sysUserVo.getPageNum(), sysUserVo.getPageSize());
        List<SysUserVo> sysUserList = sysUserMapper.findPageByQuery(sysUserVo);
        return new PageInfo<>(sysUserList);
    }

    /**
     * 根据用户名获取用户信息
     *
     * @param username 用户名
     * @return 用户信息
     */
    @Override
    public SysUser getUserByUsername(String username) {
        AssertUtil.notNull(username);
        int count = sysUserMapper.countUserByUsername(username);

        if (count == 0) {
            throw new ServiceException("用户不存在");
        }
        if (count > 1) {
            throw new ServiceException("用户名重复");
        }

        return sysUserMapper.getUserByUsername(username);
    }

    /**
     * 通过用户id得到所有的权限code
     *
     * @param userId   用户ID
     * @param roleList 用户角色
     * @return 权限标识code
     */
    @Override
    public Set<String> listElementByUserId(String userId, List<SysRole> roleList) {
        if (CollectionUtils.isEmpty(roleList)) {
            return Collections.emptySet();
        }
        // 获取角色ID
        List<String> roleIds = roleList.stream().map(SysRole::getId).collect(Collectors.toList());
        // 获取权限code
        List<String> codeList = sysRolePermissionMapper.findElementPermissionCodeByRoleId(roleIds);
        return new HashSet<>(codeList);
    }

    /**
     * 通过用户id得到所有的角色
     *
     * @param id 用户ID
     * @return 结果
     */
    @Override
    public List<SysRole> listRolesByUserId(String id) {
        return sysRoleMapper.getByUserId(id);
    }

    /**
     * 修改密码
     *
     * @param sysUser 用户对象
     */
    @Override
    @Transactional(readOnly = false)
    public void updatePassword(SysUserVo sysUser) {
        AssertUtil.notNull(sysUser);
        AssertUtil.notNull(sysUser.getNewPwd());
        AssertUtil.notNull(sysUser.getOldPwd());

        // 新密码和再次输入密码一致性校验
        if (!sysUser.getNewPwd().equals(sysUser.getPassword())) {
            throw new ServiceException("新密码与确认密码不一致");
        }

        // 新密码长度校验
        if (sysUser.getNewPwd().length() > Constant.MAXSIZE || sysUser.getNewPwd().length() < Constant.MINSIZE) {
            throw new ServiceException("密码长度最小是8位，最长是18位");
        }

        // 新密码正则校验
        if (!Pattern.matches(CommonConstant.REGCH, new String(Base64.getDecoder().decode(sysUser.getPassword())))) {
            throw new ServiceException("您的新密码复杂度太低（密码中必须包含字母、数字、特殊字符）");
        }

        // 获取用户信息
        SysUser user = sysUserMapper.selectByPrimaryKey(sysUser.getId());
        AssertUtil.notNull(user, "用户不存在");

        // 原密码正确性校验
        String oldPassword = new String(Base64.getDecoder().decode(sysUser.getOldPwd()));
        if (!bp.matches(oldPassword, user.getPassword())) {
            throw new ServiceException("旧密码错误");
        }

        SysUser record = new SysUser();
        record.setId(sysUser.getId());
        String password = bp.encode(new String(Base64.getDecoder().decode(sysUser.getPassword())));
        record.setPassword(password);
        sysUserMapper.updateByPrimaryKeySelective(record);
    }

    /**
     * 通过主键修改对应用户得密码
     *
     * @param sysUserVo 用户对象
     * @return 结果
     */
    @Override
    @Transactional(readOnly = false)
    public void resetPassword(SysUserVo sysUserVo) {
        AssertUtil.notNull(sysUserVo);
        AssertUtil.notNull(sysUserVo.getId());
        AssertUtil.notNull(sysUserVo.getPassword());

        SysUser sysUser = sysUserMapper.selectByPrimaryKey(sysUserVo.getId());
        AssertUtil.notNull(sysUser, "用户不存在");

        SysUser record = new SysUser();
        record.setId(sysUser.getId());
        String password = bp.encode(new String(Base64.getDecoder().decode(sysUserVo.getPassword())));
        record.setPassword(password);
        sysUserMapper.updateByPrimaryKeySelective(record);
    }

    /**
     * 对前端提交的数据做校验
     *
     * @param sysUser 前端提交的数据
     */
    private void checkRegisterData(SysUser sysUser) {
        // 校验用户名
        if (StringUtils.isEmpty(sysUser.getUsername())) {
            throw new ServiceException("用户登录名必须填写");
        }
        if (StringUtils.isEmpty(sysUser.getPhone())) {
            throw new ServiceException("手机号必须填写");
        }
        // 首先根据用户的登录名 查询username是否重复
        validateUsernameAndPhone(sysUser.getUsername(), sysUser.getPhone());
        // 校验用户密码
        if (StringUtils.isEmpty(sysUser.getPassword())) {
            throw new ServiceException("用户密码必须填写");
        }
    }

    /**
     * 判断用户名是否存在
     *
     * @param username 用户名
     */
    @Override
    public void existsUserByUsername(String username) {
        AssertUtil.notNull(username);
        int count = sysUserMapper.countUserByUsername(username);
        if (count > 0) {
            throw new ServiceException("用户名已存在");
        }
    }

    /**
     * 密码复杂度低时强制修改密码
     * @param sysUser
     * @param ip
     */
    @Override
    @Transactional(readOnly = false)
    public void uppwd(SysUserVo sysUser, String ip) {
        //ip  + 用户名 双重锁
        if (loginAttemptService.isBlocked(ip)) {
            throw new ServiceException("操作次数过多,请十分钟后再试!");
        }
        try {
            sysUser.setUsername(RSAEncrypt.privateKeyDecrypt(sysUser.getUsername(), TokenConstants.PRIVATE_KEY));
            sysUser.setOldPwd(RSAEncrypt.privateKeyDecrypt(sysUser.getOldPwd(), TokenConstants.PRIVATE_KEY));
            sysUser.setNewPwd(RSAEncrypt.privateKeyDecrypt(sysUser.getNewPwd(), TokenConstants.PRIVATE_KEY));
            sysUser.setPassword(RSAEncrypt.privateKeyDecrypt(sysUser.getPassword(), TokenConstants.PRIVATE_KEY));
        }catch (Exception e){
            throw new ServiceException("非法请求");
        }
        if (loginAttemptService.isBlocked(sysUser.getUsername())) {
            throw new ServiceException("操作次数过多,请十分钟后再试!");
        }
        // 校验验证码
        validateCaptcha(sysUser.getCode(), sysUser.getUuid());
        //验证用户名密码复杂度
        if(StringUtils.isEmpty(sysUser.getUsername())||
                StringUtils.isEmpty(sysUser.getOldPwd())||
                StringUtils.isEmpty(sysUser.getNewPwd())||
                StringUtils.isEmpty(sysUser.getPassword())||
                StringUtils.isEmpty(sysUser.getCode())||
                StringUtils.isEmpty(sysUser.getUuid())||!sysUser.getNewPwd().equals(sysUser.getPassword())
        ){
            throw new ServiceException("非法请求");
        }
        // 新密码正则校验
        if (!Pattern.matches(CommonConstant.REGCH, sysUser.getPassword())) {
            throw new ServiceException("您的新密码复杂度太低（密码中必须包含字母、数字、特殊字符）");
        }
        try {
            SysUser sysUser2 = getUserByUsername(sysUser.getUsername());
            // 原密码正确性校验
            if (!bp.matches(sysUser.getOldPwd(), sysUser2.getPassword())) {
                throw new ServiceException("用户名或密码错误");
            }
            SysUser record = new SysUser();
            record.setId(sysUser2.getId());
            String password = bp.encode(sysUser.getPassword());
            record.setPassword(password);
            sysUserMapper.updateByPrimaryKeySelective(record);
        }catch (Exception e){
            throw new ServiceException("用户名或密码错误");
        }
    }

    @Override
    public  Map<String, String> bindSer(SysUserVo sysUser, String ip) {
        //ip  + 用户名 双重锁
        if (loginAttemptService.isBlocked(ip)) {
            throw new ServiceException("操作次数过多,请十分钟后再试!");
        }
        try {
            sysUser.setUsername(RSAEncrypt.privateKeyDecrypt(sysUser.getUsername(), TokenConstants.PRIVATE_KEY));
            sysUser.setPassword(RSAEncrypt.privateKeyDecrypt(sysUser.getPassword(), TokenConstants.PRIVATE_KEY));
        }catch (Exception e){
            throw new ServiceException("非法请求");
        }
        if (loginAttemptService.isBlocked(sysUser.getUsername())) {
            throw new ServiceException("操作次数过多,请十分钟后再试!");
        }
        // 校验验证码
        validateCaptcha(sysUser.getCode(), sysUser.getUuid());
        Map<String, String> result = new HashMap<>();
        try {
            SysUser sysUser2 = getUserByUsername(sysUser.getUsername());
            // 原密码正确性校验
            if (!bp.matches(sysUser.getPassword(), sysUser2.getPassword())) {
                throw new ServiceException("用户名或密码错误");
            }
            String uuid=UUIDUtils.getUUID();
            String serKey = GoogleAuthenticationTool.generateSecretKey();
            //生成绑定二维码
            String baseimg=GoogleAuthenticationTool.createQRCode(
                    GoogleAuthenticationTool.spawnScanQRString(
                            sysUser.getUsername(),serKey,TokenConstants.GOOGLE_AUTH_TITLE
                    )
            ,null,280,280);
            result.put("random", uuid);
            result.put("img", baseimg);
            // 将验证码文本值保存到redis
            redisUtil.set(RedisConstant.GOOGLE_VALID_CODE_KEY + uuid, serKey+"@JH@"+sysUser.getUsername(), RedisConstant.REDIS_EXPIRE_FIVE_MIN);
        }catch (Exception e){
            throw new ServiceException("用户名或密码错误");
        }
        return result;
    }

    @Override
    public void qrbindSer(SysUserVo sysUser, String ip) {
        //ip  + 用户名 双重锁
        if (loginAttemptService.isBlocked(ip)) {
            throw new ServiceException("操作次数过多,请十分钟后再试!");
        }
        if(sysUser==null||StringUtils.isEmpty(sysUser.getUuid())||StringUtils.isEmpty(sysUser.getCode())){
            throw new ServiceException("非法请求");
        }

        Object obj= redisUtil.get(RedisConstant.GOOGLE_VALID_CODE_KEY + sysUser.getUuid());
        if(obj==null){
            throw new ServiceException("二维码过期请刷新绑定二维码");
        }
        String userKey=(String)obj;
        String key=userKey.split("@JH@")[0];
        String user=userKey.split("@JH@")[1];

        if(!GoogleAuthenticationTool.getTOTPCode(key).equals(sysUser.getCode())){
            throw new ServiceException("验证码错误");
        }
        SysUser sysUser2 = getUserByUsername(user);
        if(sysUser2==null){
            throw new ServiceException("非法请求");
        }
        SysUser record = new SysUser();
        record.setId(sysUser2.getId());
        record.setSalt(key);
        sysUserMapper.updateByPrimaryKeySelective(record);
    }

    /**
     * 验证码校验
     *
     * @param validCode 验证码
     * @param guid      验证码标识
     */
    private void validateCaptcha(String validCode, String guid) {
        Object codeObj = redisUtil.get(RedisConstant.VALID_CODE_KEY + guid);
        // 验证码过期
        if (codeObj == null) {
            throw new ServiceException("验证码已过期");
        }
        // 验证码错误
        if (!validCode.equalsIgnoreCase(codeObj.toString())) {
            throw new ServiceException("验证码错误");
        }
    }
}
